Coverage Report

Created: 2024-12-19 06:34

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
D:\a\tools.proto\tools.proto\compiler\src\gen\template\util.rs
Line
Count
Source
1
// Copyright (c) 2024, BlockProject 3D
2
//
3
// All rights reserved.
4
//
5
// Redistribution and use in source and binary forms, with or without modification,
6
// are permitted provided that the following conditions are met:
7
//
8
//     * Redistributions of source code must retain the above copyright notice,
9
//       this list of conditions and the following disclaimer.
10
//     * Redistributions in binary form must reproduce the above copyright notice,
11
//       this list of conditions and the following disclaimer in the documentation
12
//       and/or other materials provided with the distribution.
13
//     * Neither the name of BlockProject 3D nor the names of its contributors
14
//       may be used to endorse or promote products derived from this software
15
//       without specific prior written permission.
16
//
17
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
21
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
22
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
24
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
25
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
26
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
29
use itertools::Itertools;
30
use regex::Regex;
31
use std::borrow::Cow;
32
33
#[allow(clippy::enum_variant_names)]
34
enum Convention {
35
    PascalCase,
36
    SnakeCase,
37
    ScreamingCase,
38
}
39
40
129
fn guess_case_convention(s: &str) -> Convention {
41
    //Assume all strings are Rust identifiers following standard Rust conventions:
42
    // snake_case, PascalCase and SCREAMING_CASE.
43
129
    let upper1 = (s.as_bytes()[0] >= b'A' && s.as_bytes()[0] <= b'Z') || 
s.as_bytes()[0] == b'_'38
;
  Branch (43:19): [True: 129, False: 0]
  Branch (43:46): [True: 91, False: 38]
  Branch (43:19): [True: 0, False: 0]
  Branch (43:46): [True: 0, False: 0]
44
129
    let upper2 = (s.as_bytes()[s.as_bytes().len() - 1] >= b'A' && 
s.as_bytes()[s.as_bytes().len() - 1] <= b'Z'89
)
  Branch (44:19): [True: 89, False: 40]
  Branch (44:67): [True: 8, False: 81]
  Branch (44:19): [True: 0, False: 0]
  Branch (44:67): [True: 0, False: 0]
45
121
        || s.as_bytes()[s.as_bytes().len() - 1] == b'_';
46
129
    if upper1 && 
upper291
{
  Branch (46:8): [True: 91, False: 38]
  Branch (46:18): [True: 8, False: 83]
  Branch (46:8): [True: 0, False: 0]
  Branch (46:18): [True: 0, False: 0]
47
8
        Convention::ScreamingCase
48
121
    } else if upper1 {
  Branch (48:15): [True: 83, False: 38]
  Branch (48:15): [True: 0, False: 0]
49
83
        Convention::PascalCase
50
    } else {
51
38
        Convention::SnakeCase
52
    }
53
129
}
54
55
38
fn capitalize(value: &str) -> Cow<str> {
56
38
    if value.is_empty() {
  Branch (56:8): [True: 0, False: 38]
  Branch (56:8): [True: 0, False: 0]
57
0
        return value.into();
58
38
    }
59
38
    if value.as_bytes()[0] >= b'A' && value.as_bytes()[0] <= b'Z' {
  Branch (59:8): [True: 38, False: 0]
  Branch (59:39): [True: 0, False: 38]
  Branch (59:8): [True: 0, False: 0]
  Branch (59:39): [True: 0, False: 0]
60
0
        value.into()
61
    } else {
62
38
        (value[..1].to_ascii_uppercase() + &value[1..]).into()
63
    }
64
38
}
65
66
0
fn decapitalize(value: &str) -> Cow<str> {
67
0
    if value.is_empty() {
  Branch (67:8): [True: 0, False: 0]
  Branch (67:8): [True: 0, False: 0]
68
0
        return value.into();
69
0
    }
70
0
    if value.as_bytes()[0] >= b'A' && value.as_bytes()[0] <= b'Z' {
  Branch (70:8): [True: 0, False: 0]
  Branch (70:39): [True: 0, False: 0]
  Branch (70:8): [True: 0, False: 0]
  Branch (70:39): [True: 0, False: 0]
71
0
        (value[..1].to_ascii_lowercase() + &value[1..]).into()
72
    } else {
73
0
        value.into()
74
    }
75
0
}
76
77
pub trait CaseConversion<'a> {
78
    fn to_pascal_case(self) -> Cow<'a, str>;
79
    fn to_snake_case(self) -> Cow<'a, str>;
80
    fn to_camel_case(self) -> Cow<'a, str>;
81
    fn to_screaming_case(self) -> Cow<'a, str>;
82
}
83
84
struct SnakeCase<'a>(&'a str);
85
struct PascalCase<'a>(&'a str);
86
struct ScreamingCase<'a>(&'a str);
87
88
impl<'a> CaseConversion<'a> for SnakeCase<'a> {
89
38
    fn to_pascal_case(self) -> Cow<'a, str> {
90
38
        self.0.split("_").map(capitalize).join("").into()
91
38
    }
92
93
0
    fn to_snake_case(self) -> Cow<'a, str> {
94
0
        self.0.into()
95
0
    }
96
97
0
    fn to_camel_case(self) -> Cow<'a, str> {
98
0
        self.0
99
0
            .split("_")
100
0
            .enumerate()
101
0
            .map(|(i, v)| if i != 0 { capitalize(v) } else { v.into() })
  Branch (101:30): [True: 0, False: 0]
  Branch (101:30): [True: 0, False: 0]
102
0
            .join("")
103
0
            .into()
104
0
    }
105
106
0
    fn to_screaming_case(self) -> Cow<'a, str> {
107
0
        self.0.split("_").map(|v| v.to_uppercase()).join("_").into()
108
0
    }
109
}
110
111
impl<'a> CaseConversion<'a> for PascalCase<'a> {
112
0
    fn to_pascal_case(self) -> Cow<'a, str> {
113
0
        self.0.into()
114
0
    }
115
116
28
    fn to_snake_case(self) -> Cow<'a, str> {
117
28
        let regex = Regex::new("[A-Z]([a-z]|[0-9])*").unwrap();
118
36
        let useless = regex.find_iter(self.0).map(|v| v.as_str().to_lowercase()).join("_").into();
119
28
        useless
120
28
    }
121
122
0
    fn to_camel_case(self) -> Cow<'a, str> {
123
0
        decapitalize(self.0)
124
0
    }
125
126
55
    fn to_screaming_case(self) -> Cow<'a, str> {
127
55
        let regex = Regex::new("[A-Z]([a-z]|[0-9])*").unwrap();
128
85
        let useless = regex.find_iter(self.0).map(|v| v.as_str().to_uppercase()).join("_").into();
129
55
        useless
130
55
    }
131
}
132
133
impl<'a> CaseConversion<'a> for ScreamingCase<'a> {
134
0
    fn to_pascal_case(self) -> Cow<'a, str> {
135
0
        self.0.split("_").map(|v| capitalize(&v.to_lowercase()).to_string()).join("").into()
136
0
    }
137
138
4
    fn to_snake_case(self) -> Cow<'a, str> {
139
4
        self.0.to_lowercase().into()
140
4
    }
141
142
0
    fn to_camel_case(self) -> Cow<'a, str> {
143
0
        self.0
144
0
            .split("_")
145
0
            .enumerate()
146
0
            .map(|(i, v)| {
147
0
                if i != 0 {
  Branch (147:20): [True: 0, False: 0]
  Branch (147:20): [True: 0, False: 0]
148
0
                    format!("{}", capitalize(&v.to_lowercase()))
149
                } else {
150
0
                    v.into()
151
                }
152
0
            })
153
0
            .join("")
154
0
            .into()
155
0
    }
156
157
4
    fn to_screaming_case(self) -> Cow<'a, str> {
158
4
        self.0.into()
159
4
    }
160
}
161
162
macro_rules! impl_case_conversion {
163
    ($s: expr, $func: ident) => {
164
        match guess_case_convention($s) {
165
            Convention::PascalCase => PascalCase($s).$func(),
166
            Convention::SnakeCase => SnakeCase($s).$func(),
167
            Convention::ScreamingCase => ScreamingCase($s).$func(),
168
        }
169
    };
170
}
171
172
impl<'a> CaseConversion<'a> for &'a str {
173
38
    fn to_pascal_case(self) -> Cow<'a, str> {
174
38
        impl_case_conversion!(
self0
, to_pascal_case)
175
38
    }
176
177
32
    fn to_snake_case(self) -> Cow<'a, str> {
178
32
        impl_case_conversion!(
self28
, to_snake_case)
179
32
    }
180
181
0
    fn to_camel_case(self) -> Cow<'a, str> {
182
0
        impl_case_conversion!(self, to_camel_case)
183
0
    }
184
185
59
    fn to_screaming_case(self) -> Cow<'a, str> {
186
59
        impl_case_conversion!(
self55
, to_screaming_case)
187
59
    }
188
}